Skip to content

Fix: browser navigations to /scope and /api/intake returned 404 - #73

Merged
robbyrob42 merged 1 commit into
primaryfrom
rob/fix-navigation-404
Sep 3, 2026
Merged

Fix: browser navigations to /scope and /api/intake returned 404#73
robbyrob42 merged 1 commit into
primaryfrom
rob/fix-navigation-404

Conversation

@robbyrob42

Copy link
Copy Markdown
Collaborator

The intake form was unreachable from a browser from the moment the property went live. Not the Turnstile hostname, which I blamed earlier today — that was wrong.

Mechanism

Cloudflare's asset router applies not_found_handling only to requests carrying Sec-Fetch-Mode: navigate. A normal request that misses the asset manifest is passed to the Worker. A navigation that misses is answered with 404.html, and the Worker is never invoked.

/scope and /api/intake are the only routes on this site rendered on demand, so neither exists in the manifest. Every browser navigation to the scoping form got the 404 page — and since a form POST is also a navigation, the intake endpoint could not be reached from a browser at all.

Reproduced exactly, on the live site:

Sec-Fetch-Dest only   200
Sec-Fetch-Site only   200
Sec-Fetch-Mode only   404   <-

Why it survived every check

  • Introduced with the 404 page in Forensics launch prep: structured data, a real 404, and product analytics #61. Before that there was no 404.html for the router to serve, so misses fell through to the Worker.
  • sites/www has the identical not_found_handling and is fine only because it ships no 404.html. Its /contact works. A comment now records that trap, because adding a 404 page there would break it the same way.
  • The property had no DNS record until today, so nothing could navigate to it.
  • Every check I ran used curl, which sends no Sec-Fetch-Mode — so it always took the Worker path and always returned 200. The bug was invisible to the exact tool used to verify it.

The fix

run_worker_first: ["/scope", "/scope/", "/api/*"] — a route list rather than true, so the Worker runs only for the two on-demand paths and the static files keep being served by the asset router without invoking it. not_found_handling: "404-page" stays, so genuine misses still get the real 404 page.

Verified locally

wrangler dev, every request carrying Sec-Fetch-Mode: navigate:

Request Before After
GET /scope 404 200
GET /scope/ 404 200
POST /api/intake 404 303 → /scope?error=captcha
GET /method/, /privacy/, /experts/robert-jacques/ 200 200
GET /nope 404 (real page) 404 (real page)

The POST returning ?error=captcha is correct — no Turnstile token in a curl request.

🤖 Generated with Claude Code

https://claude.ai/code/session_01J53xszP5CdZLXfhRJWsSyv

Cloudflare's asset router applies `not_found_handling` only to requests
carrying `Sec-Fetch-Mode: navigate`. A normal request that misses the
asset manifest is passed to the Worker; a navigation that misses is
answered with 404.html and the Worker is never invoked.

`/scope` and `/api/intake` are the only routes here rendered on demand,
so neither is in the manifest. Every browser navigation to the scoping
form got the 404 page, and because a form POST is also a navigation, the
intake endpoint was unreachable from a browser at all. The revenue path
was dead from the moment the property became reachable.

Introduced with the 404 page itself. sites/www carries the same
`not_found_handling` and is unaffected only because it ships no 404.html
for the router to serve, so its misses still fall through to the Worker
and its /contact form works. A comment there records the trap, since
adding a 404 page to that site would break it the same way.

Nothing caught it. The property had no DNS record until today, so nothing
could navigate to it, and every check run against it used curl, which
sends no Sec-Fetch-Mode and therefore always took the Worker path and
always returned 200.

`run_worker_first` lists the two on-demand routes rather than being set
to `true`, so the Worker runs only for those and the static files are
still served by the asset router without invoking it.

Verified locally against `wrangler dev` with `Sec-Fetch-Mode: navigate`:
/scope and /scope/ return 200, a POST to /api/intake returns 303 to
`?error=captcha` rather than 404, static routes still 200, and an unknown
path still returns the real 404 page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01J53xszP5CdZLXfhRJWsSyv
@robbyrob42
robbyrob42 merged commit ffe5858 into primary Sep 3, 2026
4 checks passed
@robbyrob42
robbyrob42 deleted the rob/fix-navigation-404 branch September 3, 2026 23:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant